Release 10.1A: OpenEdge Development:
Progress 4GL Handbook


Extending the test window to use a buffer handle

In this section, you’ll extend the h-CustOrderWin8.w procedure beyond the changes you already made in this chapter to illustrate the use and the value of some of the buffer handle attributes and methods. To preserve the first set of changes separately, this variant of the procedure is saved as h-CustOrderWin9.w. You’ll add buttons to the window to reposition within the current query to a particular record, and also to use a dynamic FIND method to locate and display a single record without using the query at all.

To extend the sample window to use a buffer handle:

  1. Define another variable in the Definitions section to record which of several new buttons the user selected:
  2. DEFINE VARIABLE cBtnChoice AS CHARACTER    NO-UNDO. 
    

    This variable keeps track of whether the user chose the Filter button or one of the new buttons labeled Reposition and Find that you’ll define next.

  3. Drop two new buttons onto the window beneath the Filter button. Call them btnRepos and btnFind and give them the labels Reposition and Find, respectively.
  4. Extend the existing CHOOSE trigger on btnFilter to fire for the other buttons, just as you did for the Customer field’s LEAVE trigger before:
  5. At the end of the trigger, add a line to save off which button was chosen:
  6. cBtnChoice = SELF:LABEL. /* Was this a Filter, Reposition, or Find? */ 
    

  7. Go into the LEAVE trigger for CustNum (which of course applies to all the Customer fields). Define a variable to hold the buffer handle:
  8. DEFINE VARIABLE hBuffer  AS HANDLE      NO-UNDO. 
    

  9. After the statement that assigns the query handle and PREPARE-STRING, start a block based on the value of the new cBtnChoice variable. Execute the existing code only if the button chosen was Filter:
  10. ASSIGN hQuery = QUERY CustQuery:HANDLE 
           cPrepare = hQuery:PREPARE-STRING. 
      IF cBtnChoice = "Filter" THEN 
      DO: 
    

  11. End the DO block for the Filter choice and start an ELSE block for the Reposition choice. Save off the Customer buffer handle and execute a FIND-FIRST method on it that once again uses the BEGINS keyword for CHARACTER fields and the = comparison otherwise. Execute the FIND-FIRST method as NO-ERROR in case there’s no such record:
  12. END.      /* END DO IF "Filter" */ 
    ELSE IF cBtnChoice = "Reposition" THEN  
    DO:  
        hBuffer = BUFFER Customer:HANDLE. 
        hBuffer:FIND-FIRST("WHERE " + SELF:NAME +  
                        (IF SELF:DATA-TYPE = "CHARACTER" THEN " BEGINS " 
                                ELSE " = ") +  
                            QUOTER(SELF:SCREEN-VALUE)) NO-ERROR. 
    

  13. Use the AVAILABLE attribute to check whether there was a matching record. If there was, use the REPOSITION-TO-ROWID method on the query handle to position the query to that record using its RowID. Do this operation NO-ERROR. Apply CHOOSE to the Next button to position onto the record itself, display it, and retrieve its Orders:
  14. IF hBuffer:AVAILABLE THEN  
        DO: 
          hQuery:REPOSITION-TO-ROWID(hBuffer:ROWID) NO-ERROR. 
          APPLY "CHOOSE" TO BtnNext. 
        END.  /* END DO IF AVAILABLE Customer */ 
    

    There are a few things worth examining here. First, remember that because you are using the FIND-FIRST method, and not FIND-UNIQUE, Progress retrieves a record into the buffer even if there’s more than one match. The AMBIGUOUS attribute is never true in this case.

    Second, you might be a little confused about why you have to reposition the query to the record you just retrieved in the buffer. Isn’t it already there for you to see? Yes, it is. If you leave out the REPOSITION method on the query and just display the contents of the buffer, you see the record FIND-FIRST retrieved. But if you then click the Next button, you don’t see the next record following the one FIND-FIRST retrieved. You just see the next record in the query as it was before you ever did the FIND-FIRST. The reason for this is that the FIND-FIRST method on the buffer handle is effectively reusing the buffer for a purpose completely separate from the query. There is no connection between the FIND method and the records in the query. That’s really the purpose of the FIND methods, that they allow you to fetch a specific record without using a query at all. To use the FIND method to reposition the query, you need code similar to what you just wrote, which uses the RowId to reposition the query to the same record.

    Also, remember why the NEXT operation is required. If you use one of the FIND methods on a buffer, the record you want is placed in the buffer, and you can use it immediately. But if you use one of the query handle’s REPOSITION methods, the query cursor is effectively placed immediately before the record you are repositioning to, so you need the GET NEXT statement to actually bring that record into the buffer.

    And finally, consider the NO-ERROR qualifier on the FIND-FIRST method. It’s entirely possible that the user might click the Filter button and filter the query before clicking the Reposition button, and then enter a value to reposition to that’s in the Customer table (and therefore found by the FIND-FIRST method on the buffer) but not in the query’s result set as it has been filtered. For example, the user could filter on Customer Names beginning with A, and then reposition to the first Customer Name beginning with B. Progress successfully retrieves the first Customer Name starting with B from the database, but then the REPOSITION method on the query fails because that record is not in the query’s result list. This is important to keep in mind as you use these objects and their methods.

  15. Check whether there’s a record available and display a message if there is not:
  16.      IF NOT hBuffer:AVAILABLE THEN  
           MESSAGE "No record matches that value. Try again. ". 
    END.        /* END ELSE DO (IF "Reposition" ) */ 
    

    In such a case, a record might not be available either because there was no matching record in the database or because that record was not in the query.

  17. Define a block of code to support the Find button. Because the Find button is identifying a single record, the code uses the FIND-UNIQUE buffer method:
  18. ELSE IF cBtnChoice = "Find" THEN 
        DO: 
            hBuffer = BUFFER Customer:HANDLE. 
            hBuffer:FIND-UNIQUE("WHERE " + SELF:NAME + " = " +  
                                    QUOTER(SELF:SCREEN-VALUE)) NO-ERROR. 
    

    Because you’re trying to find just one matching record, the comparison operator in the WHERE clause is simply "=".

    As before, you need to invoke the method with the NO-ERROR qualifier, in case the selection either doesn’t yield a record or yields more than one matching record. Next, you need to check for those conditions.

  19. Add code to check the AMBIGUOUS and AVAILABLE attributes to make sure you got exactly one match:
  20. IF hBuffer:AMBIGUOUS THEN 
       MESSAGE "This choice returns more than one row. Try again.". 
     ELSE IF NOT hBuffer:AVAILABLE THEN 
        MESSAGE "This choice does not match any row. Try again.". 
    

  21. Write the code to handle the successful case.
    1. Close the Customer query.
    2. Disable the navigation buttons and the Reposition button because they don’t apply if you’ve just got one record.
    3. Display the record that the FIND-UNIQUE method retrieved.
    4. Reopen the Order Browse:
    5.          ELSE DO: 
                  CLOSE QUERY CustQuery. 
                  DISABLE BtnFirst BtnNext btnPrev BtnLast BtnRepos 
                      WITH FRAME CustQuery. 
                  DISPLAY Customer.CustNum Customer.Name Customer.Address 
                      Customer.City Customer.State  
                  WITH FRAME CustQuery IN WINDOW CustWin. 
                  {&OPEN-BROWSERS-IN-QUERY-CustQuery} 
              END.    /* END DO IF "Find" */ 
          END.        /* END ELSE DO IF no errors */ 
        END.          /* END DO IF SCREEN-VALUE NOT "" */ 
      END.            /* END DO for the trigger block */ 
      

      Why close the query? Because you’re not using it. You found a record using the same buffer the query uses, but that record is not in any way related to the query.

      Why disable the buttons? Because they expect to be able to navigate through the query or reposition within it. You’d get an error if you allowed the user to choose them.

      You can copy the DISPLAY statement and the {&OPEN-BROWSERS-IN-QUERY-CustQuery} preprocessor from any of the navigation triggers. As you did in an earlier exercise, you could clean this up by factoring out the repeated code into an include file or internal procedure.

      Don’t forget to end all your blocks properly, with a comment on each END statement to help you verify the block structure as things get more complex.

      Because the Find button disables the navigation and Reposition buttons, the block of code in the same trigger for the Filter button needs to re-enable them.

  22. Add this ENABLE statement to that block, after it reopens the query:
  23.   hQuery:QUERY-OPEN(). 
      ENABLE BtnFirst BtnNext BtnPrev BtnLast BtnRepos  
            WITH FRAME CustQuery. 
      APPLY "CHOOSE" TO btnFirst. 
    

  24. Try out the procedure with various combinations of actions. When you click the Find button, for instance, it should enable the fields, accept input, and then disable the fields along with the navigation and Reposition buttons and display the one matching record:
  25. Using various other Filter, Find, and Reposition requests, you can test the various error conditions that you wrote code to handle.


Copyright © 2005 Progress Software Corporation
www.progress.com
Voice: (781) 280-4000
Fax: (781) 280-4095